#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <math.h>

#include <xf86drm.h>
#include <xf86drmMode.h>

#define FILE_DRM_DEF    "/dev/dri/card0"

static int drm_fd = -1;
static drmModeRes *res = NULL;
static drmModePlaneRes *plane_res = NULL;
static drmModeConnector *conn_res = NULL;
static drmModeEncoder *enc = NULL;

static struct drm_mode_create_dumb create_dumb;
static struct drm_mode_destroy_dumb destroy_dumb;
static struct drm_mode_map_dumb map_dumb;
static uint32_t buf_id = 0;
static uint8_t *buf_map = 0;
static uint32_t buf_id_cur = 0;
static uint8_t *buf_map_cur = 0;

static uint8_t dual_buffer = 1;
static struct drm_mode_create_dumb create_dumb_dual;
static struct drm_mode_destroy_dumb destroy_dumb_dual;
static struct drm_mode_map_dumb map_dumb_dual;
static uint32_t buf_id_dual = 0;
static uint8_t *buf_map_dual = 0;

static drmModeCrtc *crtc = NULL;
static drmModeModeInfo cur_mode;

#define RED_FULL    0xFF0000
#define GREEE_FULL  0x00FF00
#define BLUE_FULL   0x0000FF

typedef struct {
    int x;
    int y;
} coo_d_t;

#define MEM_LOCATION_32(x, y)   ((uint32_t *)buf_map_cur + y*create_dumb.width + x)

static int draw_margin_line(void)
{
    int x, y;

    for (x=0; x<create_dumb.width; x++) {/* y=0 */
        y = 0;
        *(MEM_LOCATION_32(x,y)) = GREEE_FULL;
    }

    for (y=0; y<create_dumb.height; y++) {/* x=width */
        x = create_dumb.width-1;
        *(MEM_LOCATION_32(x,y)) = GREEE_FULL;
    }

    for (x=0; x<create_dumb.width; x++) {/* y=height */
        y = create_dumb.height-1;
        *(MEM_LOCATION_32(x,y)) = GREEE_FULL;
    }

    for (y=0; y<create_dumb.height; y++) {/* x=0 */
        x = 0;
        *(MEM_LOCATION_32(x,y)) = GREEE_FULL;
    }

    return 0;
}

static int draw_line(int x1, int y1, int x2, int y2, int color)
{
    int x, y, t;
    float a, b;

    if (x1 == x2) {
        if (y1 > y2) {
            t = y1;
            y1 = y2;
            y2 = t;
        }
        for (y=y1; y<y2; y++) {
            x = x1;
            if (x>=0 && x<create_dumb.width && y>=0 && y<create_dumb.height)
                *(MEM_LOCATION_32(x,y)) = color;
        }
    }

    /* y = ax + b */
    a = (float)(y2-y1)/(x2-x1);
    b = (float)((y1+y2) - a*(x1+x2))/2;

    if (x1 > x2) {
        t = x1;
        x1 = x2;
        x2 = t;
    }
    for (x=x1; x<=x2; x++) {
        y = a*x + b;
        if (x>=0 && x<create_dumb.width && y>=0 && y<create_dumb.height)
            *(MEM_LOCATION_32(x,y)) = color;
    }

    if (y1 > y2) {
        t = y1;
        y1 = y2;
        y2 = t;
    }
    for (y=y1; y<y2; y++) {
        x = (y-b)/a;
        if (x>=0 && x<create_dumb.width && y>=0 && y<create_dumb.height)
            *(MEM_LOCATION_32(x,y)) = color;
    }

    return 0;
}

static int draw_circle(int a, int b, int r, uint32_t color)
{
    /* circle: center(a,b), r(r), (x-a)^2 + (y-b)^2 = r^2 */
    int x, y;

    for (x = a-r; x <= a+r; x++) {
        y = (sqrtf(r*r-((x-a)*(x-a)))+0.5) + b;
        if (x>=0 && x<create_dumb.width && y>=0 && y<create_dumb.height)
            *(MEM_LOCATION_32(x,y)) = color;

        y = b - (sqrtf(r*r-((x-a)*(x-a)))+0.5);
        if (x>=0 && x<create_dumb.width && y>=0 && y<create_dumb.height)
            *(MEM_LOCATION_32(x,y)) = color;
    }

    for (y = b-r; y <= b+r; y++) {
        x = (sqrtf(r*r-((y-b)*(y-b)))+0.5) + a;
        if (x>=0 && x<create_dumb.width && y>=0 && y<create_dumb.height)
            *(MEM_LOCATION_32(x,y)) = color;

        x = a - (sqrtf(r*r-((y-b)*(y-b)))+0.5);
        if (x>=0 && x<create_dumb.width && y>=0 && y<create_dumb.height)
            *(MEM_LOCATION_32(x,y)) = color;
    }

    return 0;
}

static int circle_line_cross(coo_d_t p1, coo_d_t p2, coo_d_t o, int r)
{
    int x, y, t, c_x, c_y;
    float a, b;

    if (p1.x == p2.x) {
        if (p1.y > p2.y) {
            t = p1.y;
            p1.y = p2.y;
            p2.y = t;
        }
        for (y=p1.y; y<p2.y; y++) {
            x = p1.x;
            if (x>=0 && x<create_dumb.width && y>=0 && y<create_dumb.height) {
                c_y = (sqrtf(r*r - ((x-o.x)*(x-o.x)))+0.5) + o.y;
                if (y == c_y) return 1;
                c_y = o.y - (sqrtf(r*r - ((x-o.x)*(x-o.x)))+0.5);
                if (y == c_y) return 1;
            }
        }
    }

    /* y = ax + b */
    a = (float)(p2.y-p1.y)/(p2.x-p1.x);
    b = (float)((p1.y+p2.y) - a*(p1.x+p2.x))/2;

    if (p1.x > p2.x) {
        t = p1.x;
        p1.x = p2.x;
        p2.x = t;
    }
    for (x=p1.x; x<=p2.x; x++) {
        y = a*x + b;
        if (x>=0 && x<create_dumb.width && y>=0 && y<create_dumb.height) {
            c_y = (sqrtf(r*r - ((x-o.x)*(x-o.x)))+0.5) + o.y;
            if ((y-c_y)*(y-c_y) <= 4 ) return 1;
            c_y = o.y - (sqrtf(r*r - ((x-o.x)*(x-o.x)))+0.5);
            if ((y-c_y)*(y-c_y) <= 4 ) return 1;
        }
    }

    if (p1.y > p2.y) {
        t = p1.y;
        p1.y = p2.y;
        p2.y = t;
    }
    for (y=p1.y; y<p2.y; y++) {
        x = (y-b)/a;
        if (x>=0 && x<create_dumb.width && y>=0 && y<create_dumb.height) {
            c_x = (sqrtf(r*r - ((y-o.y)*(y-o.y)))+0.5) + o.x;
            if (x == c_x) return 1;
            if ((x-c_x)*(x-c_x) <= 4) return 1;
            c_x = o.x - (sqrtf(r*r - ((y-o.y)*(y-o.y)))+0.5);
            if ((x-c_x)*(x-c_x) <= 4) return 1;
        }
    }

    return 0;
}

static int circle_cross(coo_d_t o1, int r1, coo_d_t o2, int r2)
{
    int pitch = 0;

    pitch = sqrtf((o2.x-o1.x)*(o2.x-o1.x) + (o2.y-o1.y)*(o2.y-o1.y))+0.5;

    return pitch-r1-r2 <= 0;
}

static int bouncy_ball_line(void)
{
    static int r = 200, r_2 = 150;
    static coo_d_t o = { 200, 200 }, o_2 = { 600, 600 };
    static coo_d_t o_d = { 20, 20 }, o_d_2 = { 10, -10 };
    static coo_d_t p1 = { 600, 100 }, p2 = { 600, 400 };
    static uint32_t color_c = RED_FULL, color_c_2 = RED_FULL, color_l = BLUE_FULL;
    int sign = 1;

    draw_line(p1.x, p1.y, p2.x, p2.y, color_l);

    draw_circle(o.x, o.y, r, color_c);
    draw_circle(o_2.x, o_2.y, r_2, color_c_2);

    if (circle_cross(o, r, o_2, r_2)) {
        if (o_2.x - o.x > 0) {
            if (o_d.x > 0)
                o_d.x *= -1;
            if (o_d_2.x < 0)
                o_d_2.x *= -1;
        }

        if (o.x - o_2.x > 0) {
            if (o_d.x < 0)
                o_d.x *= -1;
            if (o_d_2.x > 0)
                o_d_2.x *= -1;
        }

        if (o_2.y - o.y > 0) {
            if (o_d.y > 0)
                o_d.y *= -1;
            if (o_d_2.y < 0)
                o_d_2.y *= -1;
        }

        if (o.y - o_2.y > 0) {
            if (o_d.y < 0)
                o_d.y *= -1;
            if (o_d_2.y > 0)
                o_d_2.y *= -1;
        }

        color_c = random();
        color_c_2 = random();

        if (random()%2) sign = 1;
        else sign = -1;
        o_d.x += (random()%10)*sign;
        o_d.y += (random()%10)*sign;
        o_d_2.x += (random()%10)*sign;
        o_d_2.y += (random()%10)*sign;
    }

    if ((o.x-r<0 && o_d.x<0) || (o.x+r>=create_dumb.width && o_d.x>0)) {
        o_d.x *= -1;
        color_c = random();
    }
    if ((o.y-r<0 && o_d.y<0) || (o.y+r>=create_dumb.height&& o_d.y>0)) {
        o_d.y *= -1;
        color_c = random();
    }

    if ((o_2.x-r_2<0 && o_d_2.x<0) || (o_2.x+r_2>=create_dumb.width && o_d_2.x>0)) {
        o_d_2.x *= -1;
        color_c_2 = random();
    }
    if ((o_2.y-r_2<0 && o_d_2.y<0) || (o_2.y+r_2>=create_dumb.height&&o_d_2.y>0)) {
        o_d_2.y *= -1;
        color_c_2 = random();
    }

    while (circle_line_cross(p1, p2, o, r) || circle_line_cross(p1, p2, o_2, r_2)) {
        p1.x = random()%create_dumb.width;
        p1.y = random()%create_dumb.height;
        p2.x = random()%create_dumb.width;
        p2.y = random()%create_dumb.height;
        color_l = random();
    }

    o.x += o_d.x;
    o.y += o_d.y;

    o_2.x += o_d_2.x;
    o_2.y += o_d_2.y;

    return 0;
}

static int clear_screen(void)
{
    memset(buf_map_cur, 0x0, create_dumb.size);
}

int main(int argc, char *argv[])
{
    int ret = 0, i = 0, j = 0;
    char *file_drm = NULL;
    uint64_t has_dumb = 0;
    int mode_index = 0;

    if (argc > 1)
        file_drm = argv[1];
    else
        file_drm = FILE_DRM_DEF;
    drm_fd = open(file_drm, O_RDWR);
    if (drm_fd < 0) {
        printf("open %s failed(%d:%s)\n", file_drm, errno, strerror(errno));
        return -errno;
    }

    if (argc > 2) {
        mode_index = atoi(argv[2]);
    }

    ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &has_dumb);
    if (ret < 0 || has_dumb == 0) {
        printf("drm device '%s' does not support dumb buffers(%d, %ld)\n", file_drm, ret, has_dumb);
        goto out;
    }

    res = drmModeGetResources(drm_fd);
    if (res == NULL) {
        printf("drm device '%s' can't retrieve resources\n", file_drm);
        goto out;
    }

    printf("\ndrmModeRes:\n");
    printf("\tcount_fbs\t\t%d\n", res->count_fbs);
    printf("\tfbs\t\t\t%d", res->fbs==NULL?0:res->fbs[0]);
    for (i=1; i<res->count_fbs; i++) printf(" %d", res->fbs[i]);
    printf("\n");
    printf("\tcount_crtcs\t\t%d\n", res->count_crtcs);
    printf("\tcrtcs\t\t\t%d", res->crtcs==NULL?0:res->crtcs[0]);
    for (i=1; i<res->count_crtcs; i++) printf(" %d", res->crtcs[i]);
    printf("\n");
    printf("\tcount_encoders\t\t%d\n", res->count_encoders);
    printf("\tencoders\t\t%d", res->encoders==NULL?0:res->encoders[0]);
    for (i=1; i<res->count_encoders; i++) printf(" %d", res->encoders[i]);
    printf("\n");
    printf("\tcount_connectors\t%d\n", res->count_connectors);
    printf("\tconnectors\t\t%d", res->connectors==NULL?0:res->connectors[0]);
    for (i=1; i<res->count_connectors; i++) printf(" %d", res->connectors[i]);
    printf("\n");
    printf("\twidth\t\t\t%d-%d\n", res->min_width, res->max_width);
    printf("\theight\t\t\t%d-%d\n", res->min_height, res->max_height);

    drmSetClientCap(drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
    plane_res = drmModeGetPlaneResources(drm_fd);
    printf("\ndrmModePlaneRes:\n");
    printf("\tcount_planes\t\t%d\n", plane_res->count_planes);
    printf("\tplanes\t\t\t%d", plane_res->planes==NULL?0:plane_res->planes[0]);
    for (i=1; i<plane_res->count_planes; i++) printf(" %d", plane_res->planes[i]);
    printf("\n");

    conn_res = drmModeGetConnector(drm_fd, res->connectors[0]);
    if (conn_res == NULL) {
        printf("drm device '%s' can't retrieve connector %d: %d(%s)\n", file_drm, res->connectors[0], errno, strerror(errno));
        goto out;
    }

    if (conn_res->connection != DRM_MODE_CONNECTED || conn_res->count_modes == 0) {
        goto out;
    }

    if (conn_res->encoder_id > 0)
        enc = drmModeGetEncoder(drm_fd, conn_res->encoder_id);

    if (enc == NULL) {
        for (i=0; i<res->count_encoders; i++) {
            enc = drmModeGetEncoder(drm_fd, res->encoders[i]);
            if (enc == NULL) continue;
            for (j=0; j<res->count_crtcs; j++) {
                if ((enc->possible_crtcs & (1<<j)) == 0) continue;

                if (res->crtcs[j] > 0) break;
            }

            if (j < res->count_crtcs) {
                enc->crtc_id = res->crtcs[j];
                conn_res->encoder_id = enc->encoder_id;
                break;
            }

            drmModeFreeEncoder(enc);
            enc = NULL;
        }
    }

    printf("\nconnector-%d connect to encoder-%d to crtc-%d, mode:\n", conn_res->connector_id, conn_res->encoder_id, enc->crtc_id);
    for (j=0; j<conn_res->count_modes; j++) {
        printf("%d%c\t%4d x %-4d\t:clock-%-6d, sync_start-%-4d(%-4d), sync_end-%-4d(%-4d), total-%-4d(%-4d), hskew(vscan)-%d(%d)\n", j, j==mode_index?'*':'.',
            conn_res->modes[j].hdisplay, conn_res->modes[j].vdisplay, conn_res->modes[j].clock, conn_res->modes[j].hsync_start, conn_res->modes[j].vsync_start,
            conn_res->modes[j].hsync_end, conn_res->modes[j].vsync_end, conn_res->modes[j].htotal, conn_res->modes[j].vtotal, conn_res->modes[j].hskew, conn_res->modes[j].vscan);
    }
    printf("\n");

    memset(&cur_mode, 0, sizeof(cur_mode));
    memcpy(&cur_mode, &conn_res->modes[mode_index], sizeof(cur_mode));

    memset(&create_dumb, 0, sizeof(create_dumb));
    create_dumb.width = cur_mode.hdisplay;
    create_dumb.height = cur_mode.vdisplay;
    create_dumb.bpp = 32;
    ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
    if (ret < 0) {
        printf("drm device '%s' can't create dumb buffer-%d(%s)\n", file_drm, errno, strerror(errno));
        goto out;
    }

    memset(&destroy_dumb, 0, sizeof(destroy_dumb));
    destroy_dumb.handle = create_dumb.handle;

    ret = drmModeAddFB(drm_fd, create_dumb.width, create_dumb.height, 24, create_dumb.bpp, create_dumb.pitch, create_dumb.handle, &buf_id);
    if (ret < 0) {
        printf("drm device '%s' can't create framebuffer-%d(%s)\n", file_drm, errno, strerror(errno));
        goto out;
    }

    memset(&map_dumb, 0, sizeof(map_dumb));
    map_dumb.handle = create_dumb.handle;
    ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
    if (ret < 0) {
        printf("drm device '%s' can't map dumb buffer-%d(%s)\n", file_drm, errno, strerror(errno));
        goto out;
    }

    buf_map = mmap(0, create_dumb.size, PROT_READ|PROT_WRITE, MAP_SHARED, drm_fd, map_dumb.offset);
    if (buf_map==MAP_FAILED || buf_map==NULL) {
        printf("drm device '%s' can't mmap dumb buffer-%d(%s)\n", file_drm, errno, strerror(errno));
        ret = -errno;
        goto out;
    }
    memset(buf_map, 0x0, create_dumb.size);
    printf("drm device '%s' add framebuffer(%dx%dx%d, pitch=%d, map=%p(0x%llx))\n",
            file_drm, create_dumb.width, create_dumb.height, create_dumb.bpp, create_dumb.pitch, buf_map, map_dumb.offset);

    if (dual_buffer) {
        memset(&create_dumb_dual, 0, sizeof(create_dumb_dual));
        create_dumb_dual.width = cur_mode.hdisplay;
        create_dumb_dual.height = cur_mode.vdisplay;
        create_dumb_dual.bpp = 32;
        ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb_dual);
        if (ret < 0) {
            printf("drm device '%s' can't create dumb_dual buffer-%d(%s)\n", file_drm, errno, strerror(errno));
            goto out;
        }

        memset(&destroy_dumb_dual, 0, sizeof(destroy_dumb_dual));
        destroy_dumb_dual.handle = create_dumb_dual.handle;

        ret = drmModeAddFB(drm_fd, create_dumb_dual.width, create_dumb_dual.height, 24, create_dumb_dual.bpp, create_dumb_dual.pitch, create_dumb_dual.handle, &buf_id_dual);
        if (ret < 0) {
            printf("drm device '%s' can't create framebuffer-%d(%s)\n", file_drm, errno, strerror(errno));
            goto out;
        }

        memset(&map_dumb_dual, 0, sizeof(map_dumb_dual));
        map_dumb_dual.handle = create_dumb_dual.handle;
        ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb_dual);
        if (ret < 0) {
            printf("drm device '%s' can't map dumb buffer-%d(%s)\n", file_drm, errno, strerror(errno));
            goto out;
        }

        buf_map_dual = mmap(0, create_dumb_dual.size, PROT_READ|PROT_WRITE, MAP_SHARED, drm_fd, map_dumb_dual.offset);
        if (buf_map_dual==MAP_FAILED || buf_map_dual==NULL) {
            printf("drm device '%s' can't mmap dumb buffer-%d(%s)\n", file_drm, errno, strerror(errno));
            ret = -errno;
            goto out;
        }
        memset(buf_map_dual, 0x0, create_dumb_dual.size);
        printf("drm device '%s' add framebuffer_dual(%dx%dx%d, pitch=%d, map=%p(0x%llx))\n",
                file_drm, create_dumb_dual.width, create_dumb_dual.height, create_dumb_dual.bpp, create_dumb_dual.pitch, buf_map_dual, map_dumb_dual.offset);
    }

    crtc = drmModeGetCrtc(drm_fd, enc->crtc_id);

    buf_id_cur = buf_id;
    buf_map_cur = buf_map;
    while (1) {
        clear_screen();

        draw_margin_line();

        bouncy_ball_line();

        ret = drmModeSetCrtc(drm_fd, enc->crtc_id, buf_id_cur, 0, 0, &conn_res->connector_id, 1, &cur_mode);
        if (ret < 0) {
            printf("drm device '%s' can't set crtc's mode-%d(%s)\n", file_drm, errno, strerror(errno));
            goto out;
        }

        usleep(50*1000);
        if (dual_buffer) {
            buf_id_cur = buf_id_cur == buf_id ? buf_id_dual : buf_id_cur;
            buf_map_cur = buf_map_cur == buf_map ? buf_map_dual : buf_map_cur;
        }
    }

out:
    if (buf_map != NULL) {
        munmap(buf_map, create_dumb.size);
        buf_map = NULL;
    }
    if (buf_id) {
        drmModeRmFB(drm_fd, buf_id);
        buf_id = 0;
    }
    if (destroy_dumb.handle > 0) {
        drmIoctl(drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb);
        destroy_dumb.handle = 0;
    }

    if (dual_buffer) {
        if (buf_map_dual != NULL) {
            munmap(buf_map_dual, create_dumb_dual.size);
            buf_map_dual = NULL;
        }
        if (buf_id_dual) {
            drmModeRmFB(drm_fd, buf_id_dual);
            buf_id_dual = 0;
        }
        if (destroy_dumb_dual.handle > 0) {
            drmIoctl(drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb_dual);
            destroy_dumb_dual.handle = 0;
        }
    }

    if (enc != NULL) {
        drmModeFreeEncoder(enc);
        enc = NULL;
    }
    if (conn_res != NULL) {
        drmModeFreeConnector(conn_res);
        conn_res = NULL;
    }
    if (res != NULL) {
        drmModeFreeResources(res);
        res = NULL;
    }
    if (drm_fd >= 0) {
        close(drm_fd);
        drm_fd = -1;
    }

    return ret;
}
